深入理解VFIO驱动框架
作者介绍:
Jack,目前就职于通信行业某上市公司,主要从事Linux相关系统软件开发工作,负责基带芯片Soc芯片建模仿真以及虚拟化系统软件开发,基带芯片soc芯片BringUp及驱动开发,喜欢阅读内核源代码,在不断的学习和工作中深入理解外设虚拟化,网络虚拟化,用户态驱动等内核子系统。
VFIO(Virtual Function I/O)驱动框架是一个用户态驱动框架,在intel平台它充分利用了VT-d等技术提供的DMA Remapping和Interrupt Remapping特性, 在保证直通设备的DMA安全性同时可以达到接近物理设备的I/O的性能。VFIO是一个可以安全的把设备I/O、中断、DMA等暴露到用户空间,用户态进程可以直接使用VFIO驱动访问硬件,从而可以在用户空间完成设备驱动的框架。在内核源码附带的文档<<Documentation/vfio.txt>>中对VFIO描述的相关概念有比较清楚的描述,也对VFIO的使用方法有清楚的描述。对于VT-d,DMAR 等硬件技术细节,可以参考VT-d的SPEC等文档,本文不对具体的物理硬件特性进行描述,涉及到相关部分只是做简单介绍。
在VFIO驱动框架中,有几个核心概念需要理解。包括:IOMMU,device , group ,container。
IOMMU是一个硬件单元,它可以把设备的IO地址映射成虚拟地址,为设备提供页表映射,设备通过IOMMU将数据直接DMA写到用户空间。
Device 是指要操作的硬件设备,这里的设备需要从IOMMU拓扑的角度来看。如果device 是一个硬件拓扑上是独立那么这个设备构成了一个IOMMU group。如果多个设备在硬件是互联的,需要相互访问数据,那么这些设备需要放到一个IOMMU group 中隔离起来。
Group 是IOMMU 能进行DMA隔离的最小单元。一个group 可以有一个或者多个device。
container 是由多个group 组成。虽然group 是VFIO 的最小隔离单元,但是并不是最好的分割粒度,比如多个group 需要共享一个页表的时候。将多个group 组成一个container来提高系统的性能,也方便用户管理。一般一个进程作为一个container。
下图描述了device , group ,container和用户进程(app)的关系。
图1 container、group与device关系
01
VFIO 驱动框架设计分析
VFIO 驱动框架设计的比较清晰,最上层的vifo interface 是和用户进行ioctl 交互的接口。在内核源码中代码路径为:drivers\vfio\vfio.c。vifo_iommu 是对IOMMU driver 的封装,为vifo interface 提供IOMMU 功能,在内核源码中代码路径为:drivers\vfio\vfio_iommu_type1.c。iommu driver 是物理硬件的IOMMU 实现,例如intel VT-D。vfio_pci 是的device 驱动的封装,为vfio interface 提供设备的访问能力,例如访问设备的配置空间,bar空间。在内核源码中代码路径为:drivers\vfio \pci\ vfio_pci.c 。pci_bus driver 是物理PCI 设备的驱动。VFIO的中断重映射相关的部分需要有kvm 相关的代码分析,本文没有分析。
图2 vfio 驱动框架
02
VFIO 用户接口三个层面
VFIO 给用户空间提供的接口主要是有三个层面上的,第一个是container 层面,第二个是group 层面,第三个是device层面。
第一个层面,container的操作是通过打开/dev/vifo/vifo 文件对其执行ioctl操作,主要的操作有:
VFIO_GET_API_VERSION:获取VFIO版本信息
VFIO_CHECK_EXTENSION:检测是否支持特定扩展,支持哪些类型的IOMMU
VFIO_SET_IOMMU:设置指定的IOMMU类型
VFIO_IOMMU_GET_INFO:获取IOMMU的信息
VFIO_IOMMU_MAP_DMA:指定设备端看到的IO地址到进程的虚拟地址之间的映射
第二个层面,group的操作是通过打开/dev/vifo/<group_id>文件, 对其执行ioctl操作,主要的操作有:
VFIO_GROUP_GET_STATUS:获取group 的状态信息
VFIO_GROUP_SET_CONTAINER:设置group 和container 之间的绑定关系
VFIO_GROUP_GET_DEVICE_FD:获取device 的文件描述符fd.
第三个层面的是device ,通过group 层面的ioctl的VFIO_GROUP_GET_DEVICE_FD 命令获取fd, 对获取的fd执行ioctl操作,主要的操作有:
VFIO_DEVICE_GET_REGION_INFO:用来获得设备指定区域region的数据,这里的region 不仅仅是指bar 空间还包括rom空间和配置空间。
VFIO_DEVICE_GET_IRQ_INFO:得到设备的中断信息
VFIO_DEVICE_RESET:重置设备
下图展示了用户态app,内核态VFIO, vfio-pci驱动,VFIO IOMMU 驱动,PCI驱动,IOMMU 驱动以及内核态和用户态通过三个层面的接口示意图:
图3 应用程序和VFIO接口
03
VFIO 驱动主要数据结构
static struct vfio {
struct class*class;
struct list_headiommu_drivers_list;
struct mutexiommu_drivers_lock;
struct list_headgroup_list;
struct idrgroup_idr;
struct mutexgroup_lock;
struct cdevgroup_cdev;
dev_tgroup_devt;
wait_queue_head_trelease_q;
} vfio;
struct vfio 的主要成员变量有:iommu_drivers_list:挂接在container 上的所有vfio iommu_drivers,是对IOMMU driver 的一种封装。group_list:挂接所有 vfio_group, group_idr :idr 值,关联次设备号,group_devt:group 的设备号,group_cdev:表明为一个字符设备。
struct vfio_container {
struct krefkref;
struct list_headgroup_list;
struct rw_semaphoregroup_lock;
struct vfio_iommu_driver*iommu_driver;
void*iommu_data;
boolnoiommu;
};
struct vfio_container 的主要变量有:group_list:关联到vfio_container 上的所有vifo_ group, iommu_driver: vfio_container对iommu设备驱动的封装。iommu_data:iommu_driver->open()函数的返回值,vifo_iommu对象。
struct vfio_group {
struct krefkref;
intminor;
atomic_tcontainer_users;
struct iommu_group*iommu_group;
struct vfio_container*container;
struct list_headdevice_list;
struct mutexdevice_lock;
struct device*dev;
struct notifier_blocknb;
struct list_headvfio_next;
struct list_headcontainer_next;
struct list_headunbound_list;
struct mutexunbound_lock;
atomic_topened;
wait_queue_head_tcontainer_q;
boolnoiommu;
struct kvm*kvm;
struct blocking_notifier_headnotifier;
};
struct vfio_group 的主要变量有:minor为在注册group设备时的次设备号,container_users为该group的container的计数,iommu_group为该group封装的iommu-group, container为该group关联的container,device_list将属于该group下的所有设备连接起来,vfio_next 挂接在vfio. group_list 上,container_next挂接在vfio_container. group_list上,unbound_lock 是挂在vfio_unbound_dev. unbound_next 上,opened表明该group 是否初始化完成。
struct vfio_device {
struct krefkref;
struct device*dev;
const struct vfio_device_ops*ops;
struct vfio_group*group;
struct list_headgroup_next;
void*device_data;
};
vfio_device的主要变量有:ops,指向vfio_pci_ops,group表示所属group,group_next连接同一个group 中的设备,device_data指向vfio_pci_device.
struct vfio_iommu {
struct list_headdomain_list;
struct vfio_domain*external_domain; /* domain for external user */
struct mutexlock;
struct rb_rootdma_list;
struct blocking_notifier_head notifier;
unsigned intdma_avail;
boolv2;
boolnesting;
};
vfio_iommu的主要变量有:domain_list 为该vfio_iommu下挂接的vfio_domain,external_domain 用于pci_mdev下的vfio_domain,dma_list为dma 的rb_root的根节点,dma_avail表示dma 条目数量。
struct vfio_domain {
struct iommu_domain*domain;
struct list_headnext;
struct list_headgroup_list;
intprot;/* IOMMU_CACHE */
boolfgsp;/* Fine-grained super pages */
};
vfio_domain的主要变量有:domain为对iommu_domain的封装,next挂接到vfio_iommu. domain_list, group_list是挂在该vfio_domain上的vfio_group。
struct vfio_group {
struct iommu_group*iommu_group;
struct list_headnext;
boolmdev_group;/* An mdev group */
};
vfio_group的主要成员变量有:iommu_group是对iommu_group的封装,next挂接到vfio_domain. group_list.
04
VFIO 驱动框架分析
1)vfio 驱动分析
在vfio.ko驱动加载和卸载的时候会执行vfio_init(),vfio_cleanup()函数。下面分步对这2个函数进行分析。
static int __init vfio_init(void)
{
………
ret = misc_register(&vfio_dev);
if (ret) {
pr_err("vfio: misc device register failed\n");
return ret;
}
……………
}
static void __exit vfio_cleanup(void)
{
…….
misc_deregister(&vfio_dev);
}
module_init(vfio_init);
module_exit(vfio_cleanup);
上面代码说明vifo 作为一个混杂字符设备注册进内核,混杂设备为定义为vfio_dev。
static struct miscdevice vfio_dev = {
.minor = VFIO_MINOR,
.name = "vfio",
.fops = &vfio_fops,
.nodename = "vfio/vfio",
.mode = S_IRUGO | S_IWUGO,
};
完成混杂设备注册后,会在/dev/vifo/fifo 创建设备节点,用户可以对其进行操作。具体的操作函数为vfio_fops
static const struct file_operations vfio_fops = {
.owner= THIS_MODULE,
.open= vfio_fops_open,
.release= vfio_fops_release,
.read= vfio_fops_read,
.write= vfio_fops_write,
.unlocked_ioctl= vfio_fops_unl_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl= vfio_fops_compat_ioctl,
#endif
.mmap= vfio_fops_mmap,
};
vfio_fops_open()函数主要完成struct vfio_container 对象的实例化,并将实例化的对象放到filep->private_data中。
vfio_fops_release()函数主要完成vfio_container对象的释放。
vfio_fops_read(),vfio_fops_write(),vfio_fops_mmap()主要是对vfio_iommu_driver 的read(),write(),mmap()函数的封装。
vfio_fops_unl_ioctl()函数大部分cmd 也是对vfio_iommu_driver 的ioctl()函数的封装,主要有一个VFIO_SET_IOMMU命令,完成vfio_container和vfio_iommu_driver的绑定。
static long vfio_ioctl_set_iommu(struct vfio_container *container,
unsigned long arg)
{
……
list_for_each_entry(driver, &vfio.iommu_drivers_list, vfio_next)
{
…………
if (driver->ops->ioctl(NULL, VFIO_CHECK_EXTENSION, arg) <= 0) {
module_put(driver->ops->owner);
continue;
}
data = driver->ops->open(arg);
if (IS_ERR(data)) {
ret = PTR_ERR(data);
module_put(driver->ops->owner);
continue;
}
ret = __vfio_container_attach_groups(container, driver, data);
if (ret) {
driver->ops->release(data);
module_put(driver->ops->owner);
continue;
}
container->iommu_driver = driver;
container->iommu_data = data;
break;
}
………
}
vfio_ioctl_set_iommu()函数的参数arg 是用户态进程指定vfio_iommu驱动类型,例如VFIO_TYPE1_IOMMU等。vfio_ioctl_set_iommu()会遍历vfio.iommu_drivers_list,如果driver->ops->ioctl(NULL, VFIO_CHECK_EXTENSION, arg)大于0,表明支持用户态所指定的驱动,否则继续遍历。如果遍历到了,调用vfio_iommu 驱动函数open()返回一个vfio_iommu 对象,然后执行__vfio_container_attach_groups(container, driver, data)将container 上所有group都附加到该vfio_iommu 驱动上,并且设置container 的成员变量。其中__vfio_container_attach_groups()函数最终是调用vfio_iommu驱动的attach_group()函数完成该container 上的group都附加到该vfio_iommu 驱动上。
2)vfio iommu驱动分析
以intel iommu 为例,vfio_iommu_type1.c 为vfio驱动对iommu驱动的封装。vfio_iommu_type1_init(),vfio_iommu_type1_cleanup()为vfio_iommu_type1.ko模块的加载和卸载时调用的函数。这2个函数分别主要是执行vfio_register_iommu_driver(),vfio_unregister_iommu_driver()完成vifo iommu driver 的注册和注销。具体函数如下:
static int __init vfio_iommu_type1_init(void)
{
return vfio_register_iommu_driver(&vfio_iommu_driver_ops_type1);
}
static void __exit vfio_iommu_type1_cleanup(void)
{
vfio_unregister_iommu_driver(&vfio_iommu_driver_ops_type1);
}
int vfio_register_iommu_driver(const struct vfio_iommu_driver_ops *ops)
{
struct vfio_iommu_driver *driver, *tmp;
driver = kzalloc(sizeof(*driver), GFP_KERNEL);
if (!driver)
return -ENOMEM;
driver->ops = ops;
mutex_lock(&vfio. iommu_drivers_lock);
/* Check for duplicates */
list_for_each_entry(tmp, &vfio.iommu_drivers_list, vfio_next) {
if (tmp->ops == ops) {
mutex_unlock(&vfio.iommu_drivers_lock);
kfree(driver);
return -EINVAL;
}
}
list_add(&driver->vfio_next, &vfio.iommu_drivers_list);
mutex_unlock(&vfio.iommu_drivers_lock);
return 0;
}
vfio_register_iommu_driver()函数申请一个vfio_iommu_driver 对象,并将传入的ops 挂在对象上,将vfio_iommu_driver 对象添加到vfio.iommu_drivers_list链表上。Container在执行相关操作的时候会通过vfio.iommu_drivers_list链表找到最终的操作函数ops。即:
static const struct vfio_iommu_driver_ops vfio_iommu_driver_ops_type1 = {
.name= "vfio-iommu-type1",
.owner= THIS_MODULE,
.open= vfio_iommu_type1_open,
.release= vfio_iommu_type1_release,
.ioctl= vfio_iommu_type1_ioctl,
.attach_group= vfio_iommu_type1_attach_group,
.detach_group= vfio_iommu_type1_detach_group,
.pin_pages= vfio_iommu_type1_pin_pages,
.unpin_pages= vfio_iommu_type1_unpin_pages,
.register_notifier= vfio_iommu_type1_register_notifier,
.unregister_notifier= vfio_iommu_type1_unregister_notifier,
};
vfio_iommu_driver_ops_type1 是对intel iommu 驱动的封装,最主要的功能是完成group 内的设备的内存映射和解映射。这个功能是通过vfio_iommu_type1_ioctl 的VFIO_IOMMU_MAP_DMA和VFIO_IOMMU_UNMAP_DMA命令来完成。用户空间传递的参数结构体如下:
struct vfio_iommu_type1_dma_map {
__u32argsz;
__u32flags;
#define VFIO_DMA_MAP_FLAG_READ (1 << 0)/* readable from device */
#define VFIO_DMA_MAP_FLAG_WRITE (1 << 1)/* writable from device */
__u64vaddr;/* Process virtual address */
__u64iova;/* IO virtual address */
__u64size;/* Size of mapping (bytes) */
};
内核在接收这个参数后调用vfio_dma_do_map(),vfio_dma_do_unmap()完成对内存的映射/解映射。这里以dma 映射为例说明,解映射过程可以参考内核代码。
static int vfio_dma_do_map(struct vfio_iommu *iommu,struct vfio_iommu_type1_dma_map *map)
{
dma_addr_t iova = map->iova;
unsigned long vaddr = map->vaddr;
size_t size = map->size;
int ret = 0, prot = 0;
uint64_t mask;
struct vfio_dma *dma;
……
dma = kzalloc(sizeof(*dma), GFP_KERNEL);
if (!dma) {
ret = -ENOMEM;
goto out_unlock;
}
iommu->dma_avail--;
dma->iova = iova;
dma->vaddr = vaddr;
dma->prot = prot;
get_task_struct(current->group_leader);
dma->task = current->group_leader;
dma->lock_cap = capable(CAP_IPC_LOCK);
dma->pfn_list = RB_ROOT;
ret = vfio_pin_map_dma(iommu, dma, size);
……
}
首先对入参进行检查,再申请一个dma 对象并填充其成员变量,然后对填充完了的dma 进行vfio_pin_map_dma()操作。
static int vfio_pin_map_dma(struct vfio_iommu *iommu, struct vfio_dma *dma,
size_t map_size)
{
dma_addr_t iova = dma->iova;
unsigned long vaddr = dma->vaddr;
size_t size = map_size;
long npage;
unsigned long pfn, limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
int ret = 0;
while (size) {
/* Pin a contiguous chunk of memory */
npage = vfio_pin_pages_remote(dma, vaddr + dma->size,
size >> PAGE_SHIFT, &pfn, limit);
if (npage <= 0) {
WARN_ON(!npage);
ret = (int)npage;
break;
}
/* Map it! */
ret = vfio_iommu_map(iommu, iova + dma->size, pfn, npage,
dma->prot);
if (ret) {
vfio_unpin_pages_remote(dma, iova + dma->size, pfn,
npage, true);
break;
}
size -= npage << PAGE_SHIFT;
dma->size += npage << PAGE_SHIFT;
}
dma->iommu_mapped = true;
if (ret)
vfio_remove_dma(iommu, dma);
return ret;
}
vfio_pin_pages_remote()函数是将将要映射的npage页pin 到该用户态应用程序中,然后调用vfio_iommu_map()进行dma 内存映射,vfio_iommu_map()函数调用iommu_map()函数,该函数最终使用ops->map()完成硬件的映射操作。对于vfio_iommu_driver_ops_type1,Ops为intel_iommu_ops。同理vfio_iommu_driver_ops_type1的其他成员函数最终调用intel_iommu_ops,完成对真实iommu 硬件的操作。
从上面分析可知,vfio_iommu主要功能就是完成dma 映射内存页的pin操作和相关内存管理以及对硬件iommu驱动的封装,同时也为vfio 的container提供接口操作。
3)vfio group驱动分析
继续回到vfio_init()函数,
static int __init vfio_init(void)
{
………
/* /dev/vfio/$GROUP */
vfio.class = class_create(THIS_MODULE, "vfio");
if (IS_ERR(vfio.class)) {
ret = PTR_ERR(vfio.class);
goto err_class;
}
vfio.class->devnode = vfio_devnode;
ret = alloc_chrdev_region(&vfio.group_devt, 0, MINORMASK + 1, "vfio");
if (ret)
goto err_alloc_chrdev;
cdev_init(&vfio.group_cdev, &vfio_group_fops);
ret = cdev_add(&vfio.group_cdev, vfio.group_devt, MINORMASK + 1);
if (ret)
goto err_cdev_add;
……………
}
static void __exit vfio_cleanup(void)
{
……
idr_destroy(&vfio. group_idr);
cdev_del(&vfio.group_cdev);
unregister_chrdev_region(vfio.group_devt, MINORMASK + 1);
class_destroy(vfio.class);
vfio.class = NULL;
………
}
module_init(vfio_init);
module_exit(vfio_cleanup);
这部分代码是对vfio 中vfio_group设备的初始化相关的代码。为创建/dev/vfio/$GROUP 设备文件准备好对应的操作数据,$GROUP是对应的group id。最终使用vfio_group_fops 函数的是vfio_create_group()函数
static struct vfio_group *vfio_create_group(struct iommu_group *iommu_group)
{
……
dev = device_create(vfio.class, NULL,
MKDEV(MAJOR(vfio.group_devt), minor),
group, "%s%d", group->noiommu ? "noiommu-" : "",
iommu_group_id(iommu_group));
if (IS_ERR(dev)) {
vfio_free_group_minor(minor);
vfio_group_unlock_and_free(group);
return ERR_CAST(dev);
}
……
}
创建好/dev/vfio/$GROUP后对其设备文件操作的接口是vfio_group_fops
static const struct file_operations vfio_group_fops = {
.owner= THIS_MODULE,
.unlocked_ioctl= vfio_group_fops_unl_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl= vfio_group_fops_compat_ioctl,
#endif
.open= vfio_group_fops_open,
.release= vfio_group_fops_release,
};
vfio_group_fops_open()函数完成对vfio_group 对象的检查,该对象是vfio_create_group()中创建,同时设置该对象正在使用并加入到container 中,最后将其对象放入filep->private_data中。
vfio_group_fops_release()函数完成从container中去掉该vfio_group对象,并取消该对象正在使用。
vfio_group_fops_unl_ioctl()函数完成对vfio_group控制,其中
VFIO_GROUP_GET_STATUS:获取vfio_group 的状态
VFIO_GROUP_SET_CONTAINER:将vfio_group 和vfio_iommu 绑定
VFIO_GROUP_UNSET_CONTAINER:将vfio_group 和vfio_iommu 解绑定
VFIO_GROUP_GET_DEVICE_FD:获取vifo_device设备描述符号。
4)vfio device驱动分析
vifo_device 的创建函数为struct vfio_device *vfio_group_create_device(struct vfio_group *group,struct device *dev,const struct vfio_device_ops *ops,void *device_data)。
vifo_device 表示在VFIO 层面的设备,dev 为物理设备,ops为物理设备的操作回调,这里是vfio_pci_ops,group 表示设备所属的vfio_group,device_data保存私有数据,这里是vfio_pci_device结构体。
在vfio_group 设备文件操作的ioctl命令中,使用VFIO_GROUP_GET_DEVICE_FD 来获取设备描述符,该命令调用的函数为:static int vfio_group_get_device_fd(struct vfio_group *group, char *buf),buf中保存了用户空间传过来的设备地址(pci设备地址:domain:bus:dev:function)。
static int vfio_group_get_device_fd(struct vfio_group *group, char *buf)
{
……
device = vfio_device_get_from_name(group, buf);
if (!device)
return -ENODEV;
ret = device->ops->open(device->device_data);
if (ret) {
vfio_device_put(device);
return ret;
}
ret = get_unused_fd_flags(O_CLOEXEC);
if (ret < 0) {
device->ops->release(device->device_data);
vfio_device_put(device);
return ret;
}
filep = anon_inode_getfile("[vfio-device]", &vfio_device_fops,
device, O_RDWR);
if (IS_ERR(filep)) {
put_unused_fd(ret);
ret = PTR_ERR(filep);
device->ops->release(device->device_data);
vfio_device_put(device);
return ret;
}
/*
* TODO: add an anon_inode interface to do this.
* Appears to be missing by lack of need rather than
* explicitly prevented. Now there's need.
*/
filep->f_mode |= (FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE);
atomic_inc(&group->container_users);
fd_install(ret, filep);
……
}
首先通过group和设备名称找到设备(用户空间传进来的buf),找到后打开该设备。接着使用get_unused_fd_flags 获取一个空闲的fd,调用anon_inode_getfile获取一个文件结构体并设置该文件结构体的操作函数为vfio_device_fops,调用fd_install将空闲fd 和文件结构关联起来,最后返回该fd 给用户空间,用户空间操作该fd 的操作函数变成了vfio_device_fops操作了。
static const struct file_operations vfio_device_fops = {
.owner= THIS_MODULE,
.release= vfio_device_fops_release,
.read= vfio_device_fops_read,
.write= vfio_device_fops_write,
.unlocked_ioctl= vfio_device_fops_unl_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl= vfio_device_fops_compat_ioctl,
#endif
.mmap= vfio_device_fops_mmap,
};
vfio_device_fops 操作只是对物理设备的封装,最终调用的是vfio_pci_ops的操作。
5)vfio-pci驱动分析
通过上面对group ,device 的介绍,应用程序最终的是需要操作具体的物理设备的,具体的物理设备是挂在pci 总线上,vfio-pci 是对pci 设备的封装。vfio_pci.ko模块的加载和卸载执行函数为:
static int __init vfio_pci_init(void)
{
……
ret = pci_register_driver(&vfio_pci_driver);
……
}
static void __exit vfio_pci_cleanup(void)
{
pci_unregister_driver(&vfio_pci_driver);
……
}
static struct pci_driver vfio_pci_driver = {
.name= "vfio-pci",
.id_table= NULL, /* only dynamic ids */
.probe= vfio_pci_probe,
.remove= vfio_pci_remove,
.err_handler= &vfio_err_handlers,
};
vfio_pci_driver驱动的id_table= NULL,这就表明在驱动加载的时候无法通过pci 总线match 到该设备驱动,需要用户主动绑定/解绑vfio-pci 设备才能调用pci 设备probe/remove 函数。先看probe 函数
static int vfio_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct vfio_pci_device *vdev;
……
vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
if (!vdev) {
vfio_iommu_group_put(group, &pdev->dev);
return -ENOMEM;
}
……
ret = vfio_add_group_dev(&pdev->dev, &vfio_pci_ops, vdev);
if (ret) {
vfio_iommu_group_put(group, &pdev->dev);
kfree(vdev);
return ret;
}
……
}
vfio_pci_probe 函数主要处理的是struct vfio_pci_device *vdev,vfio_pci_device 结构体是对实际的pci设备的抽象,通过vfio_add_group_dev()函数完成vfio_pci_device和vfio_grop,vfio_device 的绑定,也实现对vifo_group,vifo_device 的创建,还实现将vifo_device的操作方法挂接到vfio_pci_device的操作方法上。
int vfio_add_group_dev(struct device *dev,const struct vfio_device_ops *ops, void *device_data) 函数的三个入参:第一个表示物理设备,第二个参数是作为vfio_device 的回调,第三个参数是vfio_pci_device 作为vfio_device一个私有数据保存下来。对于第二个参数,最终调用的是vfio_pci_ops。
static const struct vfio_device_ops vfio_pci_ops = {
.name= "vfio-pci",
.open= vfio_pci_open,
.release= vfio_pci_release,
.ioctl= vfio_pci_ioctl,
.read= vfio_pci_read,
.write= vfio_pci_write,
.mmap= vfio_pci_mmap,
.request= vfio_pci_request,
};
vfio_pci_open(),vfio_pci_release(),vfio_pci_read(),vfio_pci_write()是对物理设备pci 操作的封装。vfio_pci_mmap()是对pci 的空间进行映射,当用户空间执行mmap 操作是实现地址转换。vfio_pci_request()是通过eventfd_signal()向用户空间发生请求信号。最主要的操作是vfio_pci_ioctl()函数,通过不同命令字,对物理pci设备的操作。主要有以下操作:
VFIO_DEVICE_GET_INFO:获取设备信息,通过struct vfio_device_info 返回给用户空间。argsz表示参数大小,输入参数,flags表示设备支持的特性,regions表示区域个数,num_irqs 表示中断个数,后三个为输出参数。
struct vfio_device_info {
__u32argsz;
__u32flags;
#define VFIO_DEVICE_FLAGS_RESET(1 << 0)/* Device supports reset */
#define VFIO_DEVICE_FLAGS_PCI(1 << 1)/* vfio-pci device */
#define VFIO_DEVICE_FLAGS_PLATFORM (1 << 2)/* vfio-platform device */
#define VFIO_DEVICE_FLAGS_AMBA (1 << 3)/* vfio-amba device */
#define VFIO_DEVICE_FLAGS_CCW(1 << 4)/* vfio-ccw device */
#define VFIO_DEVICE_FLAGS_AP(1 << 5)/* vfio-ap device */
__u32num_regions;/* Max region index + 1 */
__u32num_irqs;/* Max IRQ index + 1 */
};
VFIO_DEVICE_GET_REGION_INFO:获取设备内存区域信息,通过struct vfio_region_info返回给用户空间。argsz表示参数大小,输入参数,flags表示内存区域支持的操作,输出参数,index表示区域索引,输入参数,size 表示region大小,输出参数,cap_offset,对第一个cap 的偏移,输出参数,offset表示内存区域在fd 设备文件中对应的偏移,输出参数。
struct vfio_region_info {
__u32argsz;
__u32flags;
#define VFIO_REGION_INFO_FLAG_READ(1 << 0) /* Region supports read */
#define VFIO_REGION_INFO_FLAG_WRITE(1 << 1) /* Region supports write */
#define VFIO_REGION_INFO_FLAG_MMAP(1 << 2) /* Region supports mmap */
#define VFIO_REGION_INFO_FLAG_CAPS(1 << 3) /* Info supports caps */
__u32index;/* Region index */
__u32cap_offset;/* Offset within info struct of first cap */
__u64size;/* Region size (bytes) */
__u64offset;/* Region offset from start of device fd */
};
VFIO_DEVICE_GET_IRQ_INFO:获取设备中断信息,通过struct vfio_irq_info info返回给用户空间。argsz表示参数大小,输入参数,flags表示中断支持的特性,index表示中断索引,输入参数,count 表示对应索引中断个数,输出参数。
struct vfio_irq_info {
__u32argsz;
__u32flags;
#define VFIO_IRQ_INFO_EVENTFD(1 << 0)
#define VFIO_IRQ_INFO_MASKABLE(1 << 1)
#define VFIO_IRQ_INFO_AUTOMASKED(1 << 2)
#define VFIO_IRQ_INFO_NORESIZE(1 << 3)
__u32index;/* IRQ index */
__u32count;/* Number of IRQs within this index */
};
VFIO_DEVICE_SET_IRQS:设置设备中断信息,通过struct vfio_irq_set 从用户空间设置给设备。
struct vfio_irq_set {
__u32argsz;
__u32flags;
#define VFIO_IRQ_SET_DATA_NONE(1 << 0) /* Data not present */
#define VFIO_IRQ_SET_DATA_BOOL(1 << 1) /* Data is bool (u8) */
#define VFIO_IRQ_SET_DATA_EVENTFD(1 << 2) /* Data is eventfd (s32) */
#define VFIO_IRQ_SET_ACTION_MASK(1 << 3) /* Mask interrupt */
#define VFIO_IRQ_SET_ACTION_UNMASK(1 << 4) /* Unmask interrupt */
#define VFIO_IRQ_SET_ACTION_TRIGGER(1 << 5) /* Trigger interrupt */
__u32index;
__u32start;
__u32count;
__u8data[];
};
VFIO_DEVICE_RESET:复位设备
vfio-pci驱动作为一个中间桥梁,是vfio模块和物理pci 设备之间的纽带。向下提供控制pci物理设备的行为,向上提供vfio的接口信息。对pci 设备的控制主要是获取和配置pci 的配置空间寄存器信息,中断信息等操作。
通过对vfio驱动框架中的vfio_container,vfio_iommu, vfio_group, vfio_device, vfio_pci的分析,可以看出如下操作调用关系:
图4 VFIO驱动框架通信接口
05
VFIO 驱动框架总结
VFIO驱动是内核提供的用户态驱动的一种,本文介绍了VFIO驱动框架中各个层次模块之间的调用关系,以及VFIO框架中各个层次的主要数据结构和这些数据结构的相关关系。VFIO驱动为用户空间操作外设提供了便利的接口,在设备虚拟化中具有广泛的应用。
如果大家对本文内容有任何疑问可以将您的疑问发送到(xzf20082004@163.com),我们会为您进行解答,欢迎大家踊跃提问